Route Handlers integrate with Next.js caching through segment config options: use 'force-static' to cache GET responses at build time (like SSG) and 'force-dynamic' for request-time execution (like SSR), with granular controls via revalidate and fetch options
Route Handlers in Next.js have a sophisticated relationship with the framework's caching layers. Unlike Pages Router API routes, which were always dynamic by default, Route Handlers follow a more nuanced model: they can be statically generated at build time, dynamically executed on every request, or cached with ISR-style revalidation. The default behavior changed in Next.js 15—GET handlers are now dynamic by default [citation:4], but you have explicit control through route segment configuration options that determine how and when your handlers execute and cache responses [citation:1].
GET methods: As of Next.js 15, GET handlers are dynamic by default (they run on each request), though in earlier versions they were static by default [citation:4].
Other HTTP methods: POST, PUT, PATCH, DELETE, HEAD, and OPTIONS are never cached, regardless of configuration [citation:10].
Special handlers: sitemap.ts, opengraph-image.tsx, icon.tsx and other metadata files remain static by default unless they use dynamic APIs [citation:3].
Static detection: Next.js determines if a handler can be static based on whether it uses dynamic APIs like cookies(), headers(), or request-specific properties [citation:4].
Route Handlers support ISR-style revalidation, giving you the performance of static generation with periodic updates. You can combine export const dynamic = 'force-static' with either the revalidate segment config or fetch-level next.revalidate options [citation:6]. The lowest revalidate value across the route determines the revalidation frequency [citation:1]. This is particularly useful for data that changes occasionally but doesn't need to be fresh on every request.
For more sophisticated caching strategies, Route Handlers can participate in Next.js's tag-based revalidation system. By adding tags to fetch requests, you can programmatically invalidate cached responses using revalidateTag() from anywhere in your application [citation:1]. This is ideal for CMS-driven content where you want to update the cache immediately when content changes, rather than waiting for a time-based revalidation window.
Check build output: Run next build and examine the route table. '○' indicates static routes, 'λ' indicates dynamic routes [citation:5].
Inspect response headers: Static routes return x-nextjs-cache: HIT or STALE; dynamic routes show MISS or no cache header [citation:7].
Use dynamic APIs intentionally: Adding cookies(), headers(), or accessing request properties automatically opts into dynamic rendering [citation:4].
Check your Next.js version: Versions prior to 15 had GET handlers static by default; 15+ defaults to dynamic [citation:4].
With the introduction of cacheComponents in Next.js 16, route handlers may be executed during build even with dynamic configuration. The recommended pattern is to use await connection() early in your handler to explicitly opt into dynamic behavior while still allowing static verification of the rest of the code [citation:2]. This ensures your handler runs at request time while maintaining build-time safety checks.
Database queries unexpectedly static: ORM queries (Prisma, Drizzle) don't automatically make handlers dynamic. Add export const dynamic = 'force-dynamic' or use headers()/cookies() [citation:4].
Environment variables during build: Static handlers evaluate at build time, so environment variables must be available then [citation:2].
Mixed methods in same file: Only GET methods can be cached; POST/PUT/DELETE remain dynamic even with force-static [citation:10].
Unexpected caching with cron jobs: For scheduled jobs that should run at runtime, use await connection() or headers() to force dynamic execution [citation:2].